﻿using System;
using System.ComponentModel;

namespace Needle.Utils.Extensions
{
    /// <summary>
    /// Action extension methods
    /// </summary>
    public static class ActionExtensions
    {

        /// <summary>
        /// Dispatches the action and result actions synchronously to a
        /// SynchronizationContext
        /// </summary>
        public static void Send<T>(this Action<T> action, T item, Action<T> onSucces, Action<Exception> onError)
        {
            if (action == null)
                throw new NullReferenceException();
            AsyncAction<T> asyncAction = new AsyncAction<T>(action, item, onSucces, onError);
            asyncAction.Send();
        }

        /// <summary>
        /// Dispatches the action and result actions asynchronously to a
        /// SynchronizationContext
        /// </summary>
        public static void Post<T>(this Action<T> action, T item, Action<T> onSucces, Action<Exception> onError)
        {
            if (action == null)
                throw new NullReferenceException();
            AsyncAction<T> asyncAction = new AsyncAction<T>(action, item, onSucces, onError);
            asyncAction.Post();
        }

        /// <summary>
        /// Dispatches the action asynchonously to the threadpool and result actions to a
        /// SynchronizationContext
        /// </summary>
        public static void BeginInvoke<T>(this Action<T> action, T item, Action<T> onSucces, Action<Exception> onError)
        {
            if (action == null)
                throw new NullReferenceException();
            AsyncAction<T> asyncAction = new AsyncAction<T>(action, item, onSucces, onError);
            asyncAction.BeginInvoke();
        }

        private class AsyncAction<T>
        {
            #region constructors

            /// <summary>
            /// Initializes a new instance of the AsyncAction class.
            /// </summary>
            public AsyncAction(Action<T> action, T arg, Action<T> onSuccess, Action<Exception> onError)
            {
                this.Arg = arg;
                this.Action = action;
                this.OnError = onError;
                this.OnSuccess = onSuccess;
                this.AsyncOperation = AsyncOperationManager.CreateOperation(this);
            }

            #endregion

            #region fields...

            private T Arg;
            private Exception Error;
            private Action<T> Action;
            private Action<Exception> OnError;
            private Action<T> OnSuccess;
            private AsyncOperation AsyncOperation;

            #endregion

            #region public methods...

            /// <summary>
            /// runs the func and the result actions in 
            /// the current SynchronizationContext synchronously.
            /// </summary>
            public void Send()
            {
                AsyncOperation.SynchronizationContext.Send(SendAction, this);
            }

            /// <summary>
            /// runs the func and the result actions in 
            /// the current SynchronizationContext asynchronously.
            /// </summary>
            public void Post()
            {
                AsyncOperation.Post(PostAction, this);
            }

            /// <summary>
            /// runs the func asynchronously on the threadpool  
            /// and runs the result actions in 
            /// the current SynchronizationContext asynchronously.
            /// </summary>
            public void BeginInvoke()
            {
                Action.BeginInvoke(Arg, EndInvoke, this);
            }

            #endregion

            private static void EndInvoke(IAsyncResult result)
            {
                var context = (AsyncAction<T>)result.AsyncState;
                try
                {
                    context.Action.EndInvoke(result);
                    if (context.OnSuccess != null)
                        context.AsyncOperation.PostOperationCompleted(PostSuccess, context);
                    else
                        context.AsyncOperation.OperationCompleted();
                }
                catch (Exception ex)
                {
                    context.Error = ex;
                    if (context.OnError != null)
                        context.AsyncOperation.PostOperationCompleted(PostError, context);
                    else
                        context.AsyncOperation.OperationCompleted();
                }
            }
            private static void SendAction(object asyncAction)
            {
                var context = (AsyncAction<T>)asyncAction;
                try
                {
                    context.Action(context.Arg);
                    if (context.OnSuccess != null)
                        PostSuccess(context);
                }
                catch (Exception ex)
                {
                    context.Error = ex;
                    if (context.OnError != null)
                        PostError(context);
                }
                finally
                {
                    context.AsyncOperation.OperationCompleted();
                }
            }
            private static void PostAction(object asyncAction)
            {
                var context = (AsyncAction<T>)asyncAction;
                try
                {
                    context.Action(context.Arg);
                    if (context.OnSuccess != null)
                        context.AsyncOperation.PostOperationCompleted(PostSuccess, context);
                    else
                        context.AsyncOperation.OperationCompleted();
                }
                catch (Exception ex)
                {
                    context.Error = ex;
                    if (context.OnError != null)
                        context.AsyncOperation.PostOperationCompleted(PostError, context);
                    else
                        context.AsyncOperation.OperationCompleted();
                }
                
            }

            private static void PostSuccess(object asyncFunc)
            {
                try
                {
                    var context = (AsyncAction<T>)asyncFunc;
                    context.OnSuccess(context.Arg);
                }
                catch  
                { /* do nothing */ }
            }

            private static void PostError(object asyncFunc)
            {
                try
                {
                    var context = (AsyncAction<T>)asyncFunc;
                    context.OnError(context.Error);
                }
                catch  
                { /* do nothing */ }
            }
        }
    }
}
